package projects.android.sms.encrypter;

import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.RSAKeyGenParameterSpec;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import org.bouncycastle.util.encoders.DecoderException;

import projects.android.sms.util.Hex;
import android.util.Base64;

/**
 * Encryption class responsible for encrypting and decrypting messages.
 * 
 * @author Thomas Jaeger
 * @author Mario Kraml
 * @author Tassilo Posegga
 * @author Robert Schenkenfelder
 * 
 */
public class Encrypter {

	public static final String KEYPAIR_ALGORITHM = "RSA";
	public static final int PUBLIC_EXPONENT = 65537;

	private static final int KEY_SIZE = 880;

	/**
	 * Generates a new public/private key pair and returns it.
	 * 
	 * @return A {@link KeyPair} object representing the public/private key pair.
	 */
	public static KeyPair generateKeyPair() {
		try {
			KeyPairGenerator kpg = KeyPairGenerator.getInstance(KEYPAIR_ALGORITHM);
			kpg.initialize(new RSAKeyGenParameterSpec(KEY_SIZE, BigInteger.valueOf(PUBLIC_EXPONENT)));
			return kpg.generateKeyPair();

		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (InvalidAlgorithmParameterException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * Encrypts a String using RSA and the given public key and returns the encrypted String.
	 * 
	 * @param data
	 *            The to be encrypted message in plain text.
	 * @param publicKey
	 *            The public key that will be used for encryption.
	 * @return The encrypted String.
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchPaddingException
	 * @throws InvalidKeyException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 */
	public static String encrypt(String data, PublicKey publicKey) throws NoSuchAlgorithmException,
			NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
		return encrypt(data, publicKey, true);
	}

	public static String encrypt(String data, PublicKey publicKey, boolean base64) throws NoSuchAlgorithmException,
			NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {

		Cipher cipher = Cipher.getInstance(KEYPAIR_ALGORITHM);
		cipher.init(Cipher.ENCRYPT_MODE, publicKey);

		byte[] message = data.getBytes();
		byte[] encrypted = cipher.doFinal(message);
		return base64 ? Base64.encodeToString(encrypted, Base64.DEFAULT) : Hex.encodeHexString(encrypted);
	}

	/**
	 * Decrypts an encrypted String using RSA and the given private key and returns the String in plain text.
	 * 
	 * @param data
	 *            The to be decrypted message.
	 * @param privateKey
	 *            The private key that will be used for decryption.
	 * @return The decrypted String.
	 * @throws Exception
	 * @throws DecoderException
	 */
	public static String decrypt(String data, PrivateKey privateKey) throws Exception {
		return decrypt(data, privateKey, true);
	}

	public static String decrypt(String data, PrivateKey privateKey, boolean base64) throws Exception {
		Cipher cipher = Cipher.getInstance(KEYPAIR_ALGORITHM);
		cipher.init(Cipher.DECRYPT_MODE, privateKey);

		byte[] encrypted = base64 ? Base64.decode(data, Base64.DEFAULT) : Hex.decodeHex(data.toCharArray());
		byte[] decrypted = cipher.doFinal(encrypted);
		return new String(decrypted);
	}
}
